package com.superbytecode.cloud.redis.aspect;

import com.superbytecode.cloud.common.context.SpringContextUtil;
import com.superbytecode.cloud.common.exception.BusinessException;
import com.superbytecode.cloud.redis.lock.Lock;
import com.superbytecode.cloud.redis.lock.LockProperties;
import com.superbytecode.cloud.redis.lock.Locker;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;

import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import javax.annotation.Resource;

/**
 * All rights Reserved, Designed By www.super-master.com
 *
 * @ProjectName: sanskrit
 * @Package: com.superbytecode.cloud.redis.lock
 * @ClassName: LockAspect
 * @Description: [分布式锁AOP]
 * <p> </p>
 * @Author: [Lance Ting]
 * @Date: 2024/8/19 11:16
 * @Version: V1.0
 * @Copyright: 2024 www.super-master.cn Inc. All rights reserved.
 * TODO: 注意,本文件Lance Ting所作,如果转载或使用请标明具体出处!
 **/
@Slf4j
@Aspect
public class LockAspect {
    private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser();
    private static final LocalVariableTableParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
    @Resource
    private LockProperties lockProperties;

    /**
     * @param joinPoint      切点
     * @param lockAnnotation 锁注解
     * @return 锁信息
     * @throws Throwable 异常
     */
    @Around("@annotation(lockAnnotation)")
    public Object around(ProceedingJoinPoint joinPoint, Lock lockAnnotation) throws Throwable {
        int waitTime = lockAnnotation.waitTime();
        TimeUnit unit = lockAnnotation.unit();
        int leaseTime = lockAnnotation.releaseTime();
        // 1、加锁操作
        Locker locker = SpringContextUtil.getBean(Locker.class);
        String lockKey = buildLockKey(joinPoint, lockAnnotation);
        log.debug("准备加锁，thread->{}，key->{}", Thread.currentThread().getId(), lockKey);
        long start = System.currentTimeMillis();
        boolean isLock = locker.lock(lockKey, leaseTime, waitTime, unit);
        // 2、加锁成功
        if (isLock) {
            try {
                return joinPoint.proceed();
            } finally {
                // 解锁,否则后续进程无法获取锁
                locker.unlock();
            }
        }
        // 3、加锁失败
        // 加锁失败，执行抛异常
        String errorMsg = lockAnnotation.errorMsg();
        long spend = System.currentTimeMillis() - start;
        String methodName = getClassAndMethodName(joinPoint);
        log.warn("警告：获取锁超时，@Lock位置->{}，设置等待时间->{}，实际等待时间->{}",//
                methodName, waitTime, spend);
        // 重置errorMsg
        errorMsg = Objects.equals("${spring.lock.errorMsg}", errorMsg) //
                ? lockProperties.getErrorMsg()//
                : errorMsg;
        // errorMsg: "'等待超过'+#waitTime+'秒，系统繁忙，请稍后再试。'"
        // boolean isSpringEl = errorMsg.contains("+") && errorMsg.contains("#");
        if (errorMsg.contains("+") && errorMsg.contains("#")) { // 如果是spEL表达式就解析它
            // 存储上下文参数信息，用于解析spEL表达式
            EvaluationContext evaluationContext = new StandardEvaluationContext();
            evaluationContext.setVariable("waitTime", waitTime);
            Expression expression = SPEL_PARSER.parseExpression(errorMsg);
            errorMsg = expression.getValue(evaluationContext, String.class);
        }
        throw new BusinessException(errorMsg);
    }

    /**
     * 构建redis key
     * <p>
     * 格式为：@lock:[series]:([key1],[key2]...)
     * </p>
     *
     * @param joinPoint      切点
     * @param lockAnnotation 锁注解
     * @return 锁
     */
    // @SuppressWarnings("all")
    private String buildLockKey(ProceedingJoinPoint joinPoint, Lock lockAnnotation) {
        // joinPoint参数解析
        Object[] args = joinPoint.getArgs();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        String classAndMethodName = getClassAndMethodName(joinPoint);
        // lockAnnotation参数解析
        String[] keys = lockAnnotation.key();
        String[] values = lockAnnotation.value();
        String series = lockAnnotation.series();
        String[] lockKey = keys.length > 0 ? keys : values;
        // 指定了组名则使用组名，否则使用类名+方法名
        String prefix = lockProperties.getPrefix();
        String lockKeyPrefix = prefix.concat(":").concat(series.isEmpty() ? classAndMethodName : series);
        if (lockKey.length == 0) {
            // 未配置@Lock的key属性
            return lockKeyPrefix;
        }
        // 解析springEl
        String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
        if (Objects.isNull(parameterNames) || parameterNames.length == 0) {
            // 方法没有入参
            log.warn("{}方法没有入参，@Lock配置的key属性将被忽略", classAndMethodName);
            return lockKeyPrefix;
        }

        EvaluationContext evaluationContext = new StandardEvaluationContext();
        // 获取方法参数值
        for (int i = 0; i < args.length; i++) {
            // 替换表达式里的变量值为实际值，p0,p1或者原参数名
            evaluationContext.setVariable(parameterNames[i], args[i]);
            evaluationContext.setVariable("p" + i, args[i]);
        }
        try {
            return Stream.of(lockKey)//
                    .map(exp -> SPEL_PARSER.parseExpression(exp).getValue(evaluationContext, String.class))//
                    .collect(Collectors.joining(",", lockKeyPrefix + ":(", ")"));
        } catch (RuntimeException e) {
            String errMegs = String.format("[%s]上的注解@Lock的key属性指定有误，无法解析spEl表达式", classAndMethodName);
            throw new EvaluationException(errMegs, e);
        }

    }

    /**
     * 获取对象方法名称
     *
     * @param joinPoint 切点
     * @return @.类名.方法名
     */
    private String getClassAndMethodName(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Class<?> clazz = joinPoint.getTarget().getClass();
        return "@".concat(clazz.getName()).concat(".").concat(method.getName());
    }
}